#ifndef CAMERA_H_
#define CAMERA_H_

#include <GL/glut.h>
#include <GL/glfw.h>

template <class vector_t>
class camera
{
private:
    vector_t __camera_position;
    vector_t __camera_view_direction;
    vector_t __camera_up_direction;
    vector_t __camera_right_direction;

    // magic constant
				static const float PIdiv180;
    // camera movement speed
				float __camera_speed;


    // private methods
    void move(const vector_t dir)
    {
        __camera_position += __camera_view_direction;
    }
    
    void move_forward(const float distance)
    {
        __camera_position -= __camera_view_direction * distance;
    }
    
    void move_upward(const float distance)
    {
        __camera_position += __camera_up_direction * distance;
    }

    void rotate_x(const float angle)
    {
        __camera_view_direction = (__camera_view_direction * cos(angle * PIdiv180) +
                             __camera_up_direction * sin(angle * PIdiv180)).normalized();
        __camera_up_direction = -(__camera_view_direction.cross(__camera_right_direction));
    }
    
    void rotate_y(const float angle)
    {
        __camera_view_direction = (__camera_view_direction * cos(angle * PIdiv180) -
                             __camera_right_direction * sin(angle * PIdiv180)).normalized();
        __camera_right_direction = __camera_view_direction.cross(__camera_up_direction);
    }
    
    void rotate_z(const float angle)
    {
        __camera_right_direction = (__camera_right_direction * cos(angle * PIdiv180) +
                                   __camera_up_direction * sin(angle * PIdiv180)).normalized();
        __camera_up_direction = - (__camera_view_direction.cross(__camera_right_direction));
    }

    void look_x(const float distance)
    {
        __camera_view_direction.x += distance;
    }
    
    void look_y(const float distance)
    {
        __camera_view_direction.y += distance;
    }

    void shift_right(const float distance)
    {
        __camera_position += __camera_right_direction * distance;
    }

    static void GLFWCALL keyboard_handler(int key, int action)
    {
        switch(key)
        {
        case GLFW_KEY_UP:
            move_forward(-__camera_speed);
            break;

        case GLFW_KEY_DOWN:
            move_forward(__camera_speed);
            break;

        case GLFW_KEY_RIGHT:
            shift_right(__camera_speed);
            break;

        case GLFW_KEY_LEFT:
            shift_right(-__camera_speed);
            break;

        case GLFW_KEY_HOME:
            move_upward(__camera_speed);
            break;

        case GLFW_KEY_END:
            move_upward(-__camera_speed);
            break;

        case GLFW_KEY_ESC:
            exit(0);
            break;

        default:
            break;
        }     
    }
    
    static void GLFWCALL mouse_handler (int x, int y)
    {
        static float oldX = 0, oldY = 0;

        look_x((x - oldX) / 50);
        look_y((oldY - y) / 50);

        oldX = x;
        oldY = y;
    }

public:
    camera(const vector_t cam_pos = 0, const vector_t cam_dir = 0, const vector_t cam_up = 0, const vector_t cam_rght = 0, const float cam_speed = 1.0f)
        : __camera_position(cam_pos),
          __camera_view_direction(cam_dir),
          __camera_up_direction(cam_up),
          __camera_right_direction(cam_rght),
          __PIdiv180(3.1415926535897932384626433832795 / 180.0),
          __camera_speed(cam_speed)
    {
    }
    
    ~camera()
    {
    }
    
    bool init()
    {
        if (glfwInit() == GL_TRUE)
        {
            glfwSetKeyCallback(keyboard_handler);
		          glfwSetMousePosCallback(mouse_handler);
            return true;
        }
        return false;
    }

    inline vector_t position() 	{ return __camera_position; }        const
    inline vector_t direction() { return __camera_view_direction; }  const
    inline vector_t up() 				   { return __camera_up_direction; }    const
    inline vector_t right()     { return __camera_right_direction; } const
    
};


#endif /* CAMERA_H_ */
